
// wtkwm.cpp
//

#include "config.h"
#include "wtk/wtkwm.hpp"
#include "wtk/wtkdisplay.hpp"
#include "wtk/wtkevent.hpp"

using namespace wtk;



// WM::WM
//
WM::WM ()
{
  // [FIXME]
  WTK_ASSERT(!current_wm);
  current_wm = this;
  
  WTK_TRACE("WM init");
}



// WM::~WM
//
WM::~WM ()
{
  WTK_TRACE("WM exit");
}



// WM::register_display
//
void WM::register_display ( Display &display )
{
  Uint32 id = display.get_sdl_window_id();
  WTK_ASSERT(!registered_displays.contains(id));
  registered_displays.emplace(id, &display);
  WTK_TRACE("display registered: %u -> %p", id, &display);
}



// WM::unregister_display
//
void WM::unregister_display ( Display &display )
{
  Uint32 id = display.get_sdl_window_id();
  WTK_ASSERT(registered_displays.at(id) == &display);
  registered_displays.erase(id);
  WTK_TRACE("display unregistered: %u -> %p", id, &display);
}



// WM::set_fly_widget
//
// [FIXME] handle destroyed widgets
//
void WM::set_fly_widget ( Widget *widget )
{
  fly_widget = widget;
}



// WM::poll_events
//
bool WM::poll_events ()
{
  SDL_Event event;
  while (SDL_PollEvent(&event))
    {
      switch (event.type)
        {
        case SDL_WINDOWEVENT:
          switch (event.window.event)
            {
            case SDL_WINDOWEVENT_CLOSE:
              WTK_TRACE("close window event, bye");
              return false;
            }
          break;
          
        case SDL_KEYDOWN:
          handle_key_down(event.key);
          break;

        case SDL_KEYUP:
          handle_key_up(event.key);
          break;

        case SDL_MOUSEMOTION:
          handle_mouse_motion(event.motion);
          break;

        case SDL_MOUSEBUTTONDOWN:
          handle_mouse_button(event.button, EventType::MouseButtonDown);
          break;

        case SDL_MOUSEBUTTONUP:
          handle_mouse_button(event.button, EventType::MouseButtonUp);
          break;

        case SDL_MOUSEWHEEL:
          handle_mouse_wheel(event.wheel);
          break;
          
          // default:
          //   WTK_TRACE("unhandled event: %d", event.type);
        }
    }
  return true;
}



// WM::update
//
void WM::update ()
{
  for (auto &it: registered_displays)
    {
      it.second->update();
    }
}



// WM::emit_event
//
void WM::emit_event ( const Event &event )
{
  // [FIXME] transmit to display
  if (event.any.widget)
    event.any.widget->event(event);
  else
    WTK_WARNING("no widget event: %d", WTK_ENUM_VALUE(event.type));
}



// WM::handle_key_down
//
void WM::handle_key_down ( SDL_KeyboardEvent &sdl_event)
{
  Event event;
  auto it = registered_displays.find(sdl_event.windowID);
  if (it == registered_displays.end())
    {
      WTK_ERROR("no display found for window ID %u", sdl_event.windowID);
      return;
    }
  Display *display = it->second;
  event.keyboard.init(EventType::KeyDown,
                      display,
                      display->get_focus_widget(),
                      sdl_event.keysym.scancode,
                      sdl_event.keysym.sym,
                      sdl_event.keysym.mod);
  emit_event(event);
}



// WM::handle_key_up
//
void WM::handle_key_up ( SDL_KeyboardEvent &sdl_event)
{
  Event event;
  auto it = registered_displays.find(sdl_event.windowID);
  if (it == registered_displays.end())
    {
      WTK_ERROR("no display found for window ID %u", sdl_event.windowID);
      return;
    }
  Display *display = it->second;
  event.keyboard.init(EventType::KeyUp,
                      display,
                      display->get_focus_widget(),
                      sdl_event.keysym.scancode,
                      sdl_event.keysym.sym,
                      sdl_event.keysym.mod);
  emit_event(event);
}



// WM::handle_mouse_motion
//
void WM::handle_mouse_motion ( SDL_MouseMotionEvent &sdl_event )
{
  Event event;

  // WTK_TRACE("mouse motion");
  
  auto it = registered_displays.find(sdl_event.windowID);
  if (it == registered_displays.end())
    {
      WTK_ERROR("no display found for window ID %u", sdl_event.windowID);
      return;
    }
  Display *display = it->second;

  int wx, wy;
  Widget *widget = display->get_widget_at(sdl_event.x, sdl_event.y, &wx, &wy);
  set_fly_widget(widget);
  if (widget)
    {
      event.motion.init(EventType::MouseMotion,
                        display,
                        widget,
                        sdl_event.state,
                        wx,
                        wy,
                        sdl_event.xrel,
                        sdl_event.yrel);
      emit_event(event);
    }
  else
    {
      WTK_TRACE("mouse motion: no widget found at %d, %d",
                sdl_event.x, sdl_event.y);
    }
}



// WM::handle_mouse_button
//
void WM::handle_mouse_button ( SDL_MouseButtonEvent &sdl_event,
                               EventType type )
{
  Event event;

  // WTK_TRACE("mouse motion");
  
  auto it = registered_displays.find(sdl_event.windowID);
  if (it == registered_displays.end())
    {
      WTK_ERROR("no display found for window ID %u", sdl_event.windowID);
      return;
    }
  Display *display = it->second;

  int wx, wy;
  Widget *widget = display->get_widget_at(sdl_event.x, sdl_event.y, &wx, &wy);
  set_fly_widget(widget);
  if (widget)
    {
      event.button.init(type,
                        display,
                        widget,
                        sdl_event.button,
                        sdl_event.state,
                        sdl_event.clicks,
                        wx,
                        wy);
      emit_event(event);
    }
  else
    {
      WTK_TRACE("mouse button: no widget found at %d, %d",
                sdl_event.x, sdl_event.y);
    }
}



// WM::handle_mouse_wheel
//
void WM::handle_mouse_wheel ( SDL_MouseWheelEvent &sdl_event )
{
  Event event;

  // WTK_TRACE("mouse motion");
  
  auto it = registered_displays.find(sdl_event.windowID);
  if (it == registered_displays.end())
    {
      WTK_ERROR("no display found for window ID %u", sdl_event.windowID);
      return;
    }
  Display *display = it->second;

  if (fly_widget)
    {
      event.wheel.init(EventType::MouseWheel,
                       display,
                       fly_widget,
                       sdl_event.x,
                       sdl_event.y);
      emit_event(event);
    }
  else
    {
      WTK_TRACE("mouse wheel: no fly widget");
    }
}
